package util

import (
	"context"
	"fmt"
	"fyne.io/fyne/v2"
	"github.com/shirou/gopsutil/disk"
	"go.uber.org/zap"
	"io/fs"
	"math"
	"os"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

func GetConfigSavePath() string {
	return fyne.CurrentApp().Storage().RootURI().Path()
}

func ToInt(value string) int {
	v, err := strconv.Atoi(value)
	if err != nil {
		zap.L().Warn("parse duration error", zap.Error(err))
	}
	return v
}

func ToDuration(value string) time.Duration {
	return time.Duration(ToInt(value))
}

func ConcatPath(parent string, child string) string {
	if parent == "" {
		return child
	}
	if child == "" {
		return parent
	}
	if parent[len(parent)-1] == '/' {
		if child[0] == '/' {
			return parent + child[1:]
		} else {
			return parent + child
		}
	} else {
		if child[0] == '/' {
			return parent + child
		} else {
			return parent + "/" + child
		}
	}
}

func ParentPath(path string) string {
	index := strings.LastIndex(path, "/")
	if index == -1 {
		return "/"
	}
	path = path[:index]
	if path == "" {
		return "/"
	}
	return path
}

func HumanReadableSize(size int64) string {
	base := 1024.0
	suffixes := []string{"B", "KB", "MB", "GB", "TB"}

	if size == 0 {
		return "0 B"
	}

	// 计算对数，以确定转换单位
	exp := int(math.Log(float64(size)) / math.Log(base))

	// 计算转换后的大小，并附上单位
	convertedSize := float64(size) / math.Pow(base, float64(exp))
	return fmt.Sprintf("%.2f %s", convertedSize, suffixes[exp])
}

type Notify struct {
	waiter       sync.Map
	ignoreNotify atomic.Bool
}

func (n *Notify) IgnoreNotify(ignore bool) {
	n.ignoreNotify.Store(ignore)
}

func (n *Notify) Notify() {
	if n.ignoreNotify.Load() {
		return
	}
	n.waiter.Range(func(key, value any) bool {
		select {
		case value.(chan struct{}) <- struct{}{}:
		default:
		}
		return true
	})
}

func (n *Notify) Wait(timeout time.Duration) {
	withTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
	defer cancelFunc()
	wc := make(chan struct{}, 1)
	n.waiter.Store(withTimeout, wc)
	defer n.waiter.Delete(withTimeout)
	select {
	case <-wc:
	case <-withTimeout.Done():
	}
}

type LocalDiskInfo struct {
	Path  string `json:"path"`
	Total uint64 `json:"total"`
	Free  uint64 `json:"free"`
}

func GetDiskInfo() ([]*LocalDiskInfo, error) {
	partitions, err := disk.Partitions(true)
	if err != nil {
		return nil, err
	}
	result := make([]*LocalDiskInfo, len(partitions))
	for i, partition := range partitions {
		item := &LocalDiskInfo{
			Path: partition.Mountpoint,
		}
		usage, err := disk.Usage(partition.Mountpoint)
		if err != nil {
			return nil, err
		}
		item.Total = usage.Total
		item.Free = usage.Free
		result[i] = item
	}
	return result, nil
}

type LocalFileInfo struct {
	IsDir    bool   `json:"is_dir"`
	Name     string `json:"name"`
	Size     int64  `json:"size"`
	ModeTime int64  `json:"mod_time"`
}

func appendFileInfo(files []fs.DirEntry, result *[]*LocalFileInfo, isDir bool) error {
	for _, f := range files {
		if f.IsDir() == isDir {
			info, err := f.Info()
			if err != nil {
				return err
			}
			*result = append(*result, &LocalFileInfo{
				IsDir:    f.IsDir(),
				Name:     f.Name(),
				Size:     info.Size(),
				ModeTime: info.ModTime().UnixMilli(),
			})
		}
	}
	return nil
}

func GetFileInfo(path string, folder bool) ([]*LocalFileInfo, error) {
	if path == "" {
		return nil, fmt.Errorf("path cannot be empty")
	}
	files, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}
	result := make([]*LocalFileInfo, 0, 0)
	if err = appendFileInfo(files, &result, true); err != nil {
		return nil, err
	}
	if !folder {
		if err = appendFileInfo(files, &result, false); err != nil {
			return nil, err
		}
	}
	return result, nil
}
